//////////////////////////////////////////////////////////////////////////////
//                                                                          //
//     Filename: MHVPXMLTranslator.cpp                                      //
//  Create date: 6/12/06                                                    //
//                                                                          //
//////////////////////////////////////////////////////////////////////////////
//                                                                          //
//  Description: This file implements the MHVPXMLTranslator class.  This    //
//                class is a protocol translator that translates from the   //
//                RTG Video Poker protocol to XML and back.                 //
//                                                                          //
//////////////////////////////////////////////////////////////////////////////
//       Copyright (c) 2006 - Realtime Gaming.  All rights reserved.        //
//////////////////////////////////////////////////////////////////////////////

#include <windows.h>
#include <stdio.h>
#include "lib.h"
#include "MHVPXMLTranslator.h"
#include "XMLDoc.h"
#include "endian.h"
#include "log.h"
#include "card.h"
#include "connect.h"

// Array holds the number of rows and columns for each pay table per
//   machine ID                   row, col
const uint8 g_aPayTableInfo[16][2] = {
                                      {  9, 6 },    // Machine ID 0, Jacks or Better
                                      { 12, 6 },    // Machine ID 1, Joker Poker
                                      { 10, 6 },    // Machine ID 2, Deuces Wild
                                      { 11, 6 },    // Machine ID 3, Aces and Eights
                                      {  9, 6 },    // Machine ID 4, All American Poker
                                      { 11, 6 },    // Machine ID 5, Bonus Poker
                                      {  9, 6 },    // Machine ID 6, Bonus Poker Deluxe
                                      { 11, 6 },    // Machine ID 7, Double Bonus Poker
                                      { 13, 6 },    // Machine ID 8, Double Double Bonus Poker
                                      { 13, 6 },    // Machine ID 9, Double Double Jackpot Poker
                                      { 13, 6 },    // Machine ID 10, Double Jackpot Poker
                                      { 10, 6 },    // Machine ID 11, Loose Deuces
                                      { 10, 6 },    // Machine ID 12, Sevens Wild
                                      { 13, 6 },    // Machine ID 13, Bonus Deuces Wild
                                      {  9, 6 },    // Machine ID 14, Pick Em Poker
                                      {  9, 6 },    // Machine ID 15, Mystery Bonus Poker
                                    };

const uint8 g_nMaxPayTables = 16;                // Total number of pay tables

//////////////////////////////////////////////////////////////////////////////
// Constructor
//////////////////////////////////////////////////////////////////////////////
MHVPXMLTranslator::MHVPXMLTranslator( Server* pServer, Connection* pParent) :
										     XMLTranslator( pServer, pParent, 7, 1 )
{
}

//////////////////////////////////////////////////////////////////////////////
// Destructor
//////////////////////////////////////////////////////////////////////////////
MHVPXMLTranslator::~MHVPXMLTranslator()
{
}

/////////////////////////////////////////////////////////////////////////////
// TranslateIncomingMessage
//////////////////////////////////////////////////////////////////////////////
bool8 MHVPXMLTranslator::TranslateIncomingMessage( uint8* pGameInBuff,
												   uint32 nGameInBuffSize,
												   uint8* pOutBuff,
												   uint32 nOutBuffSize )
{
    bool8 bRetVal = false;

	// Handle the messages.  Note, I'm only going to look at the first character
	//  in a small attempt to optimize a bit
	switch( m_sMsgID[0] )
	{
		case 'L':
			bRetVal = TranslateInLogin( m_pDoc, pGameInBuff, nGameInBuffSize );
			break;

		case 'R':	// CSN-13423: Reaload with a new denomination
			bRetVal = TranslateInReload( m_pDoc, pGameInBuff, nGameInBuffSize );
			break;

		case 'B':
			bRetVal = TranslateInBalance( m_pDoc, pGameInBuff, nGameInBuffSize, GetNumBytesMsgLen() );
			break;

		case 'D':
			// Deal message
			if ( m_sMsgID[1] == 'e' )
			{
				bRetVal = TranslateInDeal( m_pDoc, pGameInBuff, nGameInBuffSize );
			}
			// Double up message
			else if ( m_sMsgID[1] == 'o' )
			{
				// Get the action
				String sAction;
				m_pDoc->GetSingleNodeText( TEXT("//action"), sAction );

				if ( sAction == TEXT("Deny") )
				{
					bRetVal = TranslateInDoubleDeny( m_pDoc, pGameInBuff, nGameInBuffSize );
				}
				else if ( sAction == TEXT("Deal") )
				{
					bRetVal = TranslateInDoubleDeal( m_pDoc, pGameInBuff, nGameInBuffSize );
				}
				else if ( sAction == TEXT("Draw") )
				{
					bRetVal = TranslateInDoubleDraw( m_pDoc, pGameInBuff, nGameInBuffSize );
				}
				else
				{
					ASSERT(false);
				}
			}
			else
			{
				ASSERT(false);
			}
			break;

		case 'H':
			bRetVal = TranslateInHold( m_pDoc, pGameInBuff, nGameInBuffSize );
			break;

		default:
			String sMsg = TEXT("Error, received unknown client message: ");
			sMsg += m_sMsgID;
			Log::msgError( sMsg );
			break;
	}

	// Report Error
	if( !bRetVal )
	{
		if ( m_sMsgID[0] == 'L' )
		{
			// Set an error into the out buffer
			ReturnLoginError( pOutBuff, nOutBuffSize );
		}
		else
		{
			// Set an error into the out buffer
			ReturnErrorMessage( TEXT("ServerError"), pOutBuff, nOutBuffSize );

			String sMsg = TEXT("Error translating incoming message: ");
			sMsg += m_sMsgID;
			Log::msgError( sMsg );
		}
	}

	return bRetVal;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateOutgoingMessage
//////////////////////////////////////////////////////////////////////////////
uint32 MHVPXMLTranslator::TranslateOutgoingMessage( uint8* pOutBuff,
												    uint32 nOutBuffSize,
												    uint8* pGameOutBuff,
												    uint32 nGameOutBuffSize,
												    uint32 nTotalGameBytes    )
{
	bool8 bResult = true;
	uint32 nGameBytesUsed = 0;
	uint32 nOutBytesUsed = 0;

	while ( bResult && (nGameBytesUsed < nTotalGameBytes) )
	{
		// Keep track of buffers as we might be processing more than one game
		//   message at a time.
		uint8* pCurGameBuf     = pGameOutBuff     + nGameBytesUsed;
		uint32 nCurGameBufSize = nGameOutBuffSize - nGameBytesUsed;
		uint8* pCurOutBuf      = pOutBuff         + nOutBytesUsed;
		uint32 nCurOutBufSize  = nOutBuffSize     - nOutBytesUsed;

		// Make sure buffer has not been overrun!
		if ( nOutBytesUsed > nOutBuffSize )
		{
			ASSERT(false);
			Log::msgError( "Error, out buffer overflow!" );
			return 0;
		}

		// Now the actual message handlers!
		switch( pCurGameBuf[2] )
		{
			case 'L':
				bResult = TranslateOutLogin( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;

			case 'R':
				bResult = TranslateOutLogin( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize, true );
				break;

            case 'm':
				// The magic 2 here is because the outgoing protocol uses 2 bytes for the length, instead
				// of 1 as the input messages do.
				bResult = TranslateOutBalance( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize, 2 );
				break;

			case 'D':
				bResult = TranslateOutDeal( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;

			case 'H':
				bResult = TranslateOutHold( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;

			case 'O':
				bResult = TranslateOutOptions( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;

			case 'S':
				bResult = TranslateOutSubGame( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;

            case 'X':
				bResult = TranslateOutDoubleDeny( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;

            case 'U':
				bResult = TranslateOutDoubleDeal( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;

            case 'u':
				bResult = TranslateOutDoubleDraw( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;

			case 'E':
				bResult = TranslateGenericOutError( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize, 2 );
				break;

			case 'G':
			case 'N':
			case 'B':
			case '$':
			case '!':
				bResult = TranslateOutError( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;

			default:
				bResult = ReturnErrorMessage( TEXT("UnknownMessage"), pCurOutBuf, nCurOutBufSize );
				Log::msgError( "Received unknown game message: %c", pCurGameBuf[1] );
				break;
		}

		// Check and see if any errors occurred
		if ( !bResult )
		{
			ReturnErrorMessage( TEXT("ParseError"), pCurOutBuf, nCurOutBufSize );
			Log::msgError( "Error translating outgoing message: %c", pCurGameBuf[1] );
		}

		// We know that the size of the message is always stored in the first
		//  byte of the client message.
		//nGameBytesUsed += pCurGameBuf[0];
		// Actually for MHVP, the message length is stored in the first two bytes -- bwardlow 12-Jul-2006
		uint16 len = pCurGameBuf[0] + (pCurGameBuf[1] << 8);
		nGameBytesUsed += len;

		// Out XML Messages are supposed to be NULL Terminated
		nOutBytesUsed += strlen( (char*) pCurOutBuf );
		nOutBytesUsed++;  // Add one for the NULL

		DEBUGMSG( ("\n***Outgoing: %s", pCurOutBuf) );
	}

	return nOutBytesUsed;
}

//****************************************************************************
// Internal Helpers
//****************************************************************************

//////////////////////////////////////////////////////////////////////////////
// Add Cards
//////////////////////////////////////////////////////////////////////////////
bool8 MHVPXMLTranslator::AddCards( XMLDoc* pDoc,
								   uint8*  pBuff,
								   uint32& nOffset,
								   const String& sParentNode )
{
	// Loop through the cards
	for( uint8 i = 0; i < MAX_CARDS; i++ )
	{
		// Get Card value
		Card c;
		nOffset += c.deSerialize( pBuff + nOffset );

		// Build XML node string
		String sNode;
		sNode.sprintf( TEXT("card_%d"), i );

		// Add to the XML
		if ( !sParentNode.isEmpty() )
		{
			if ( !pDoc->AddChildNode( sParentNode, sNode, c ) )
			{
				return false;
			}
		}
		else
		{
			if ( !pDoc->AddRootChildNode( sNode, c ) )
			{
				return false;
			}
		}
	}

	return true;
}


//////////////////////////////////////////////////////////////////////////////
// Add Cards for Multiple hands
//////////////////////////////////////////////////////////////////////////////
bool8 MHVPXMLTranslator::AddMultiHands( XMLDoc* pDoc,
										uint8* pBuff,
										uint32& nOffset,
										const String& sParentNode,
										uint8 nNumHands)
{
	// Loop through the hands
	// XML Clients will get the hands in 'reverse' order, because that's
	// wat Flash expects.  See FB Case 15909.
	uint8 count = 0;
	uint8 handIndex = nNumHands - 1;	
	while ( count++ < nNumHands )
	{
		// Build XML node string
		String sNode;
		sNode.sprintf( TEXT("hand_%d"), handIndex );

		// Add multi-hand game node
		if ( !sParentNode.isEmpty() )
		{
			if ( !pDoc->AddChildNode( sParentNode, sNode ) )
			{
				return false;
			}
		}
		else
		{
			if ( !pDoc->AddRootChildNode( sNode ) )
			{
				return false;
			}
		}

		// Build XML node string
		sNode.sprintf( TEXT("//hand_%d"), handIndex );

		// Add cards
		if ( !AddCards( pDoc, pBuff, nOffset, sNode ) )
		{
			return false;
		}

		// Win Type
		uint8 nVal = pBuff[nOffset++];
		if ( !pDoc->AddChildNode( sNode, TEXT("win_type"), (uint32) nVal ) )
		{
			return false;
		}
		
		--handIndex;
	}

	return true;
}


//////////////////////////////////////////////////////////////////////////////
// AddPayTableInfo
//
// Adds to the XML document the strings for the current paytable.
//////////////////////////////////////////////////////////////////////////////
bool8 MHVPXMLTranslator::AddPayTableInfo( XMLDoc* pDoc,
										  uint8 nPayTable )
{
    // Make sure we have the paytable info stored
    ASSERT( m_nMachineID < g_nMaxPayTables );

    if ( m_nMachineID < g_nMaxPayTables )
    {
        uint8 nRows = g_aPayTableInfo[m_nMachineID][0];
        uint8 nCols = g_aPayTableInfo[m_nMachineID][1];

		// Add Pay Table Root Node
		if ( !pDoc->AddRootChildNode( TEXT("pay_table_text") ) )
        {
            return false;
        }

        // Send num rows and cols
        if ( !pDoc->AddChildNode( TEXT("//pay_table_text"), TEXT("num_rows"), (uint32)nRows ) )
        {
            return false;
        }
        if ( !pDoc->AddChildNode( TEXT("//pay_table_text"), TEXT("num_cols"), (uint32)nCols ) )
        {
            return false;
        }

        // Now add rows and cols
        for ( uint8 i = 0; i < nRows; i++ )
        {
		    String sRowNode;
		    sRowNode.sprintf( TEXT("row_%d"), i );

            // add the row node
            if ( !pDoc->AddChildNode( TEXT("//pay_table_text"), sRowNode ) )
            {
                return false;
            }

            // now add each column for that row
            for ( uint8 j = 0; j < nCols; j++ )
            {
		        String sColNode;
		        sColNode.sprintf( TEXT("col_%d"), j );

                String sParent = TEXT("//");
                sParent += sRowNode;

                String sStrTableKey;
                sStrTableKey.sprintf( "VP_PT_%d_%d_%d_%d", m_nMachineID, nPayTable, i, j );

                String sText;
				
				// This is the "new" model, which is required when building an XML-enabled
				// vpoker/mhvpoker server
                // Load the pay table entry from the string table
                if ( !m_pServer->getString( m_pParent->GetLanguage(),
                                                        sStrTableKey,
                                                        sText ) )
                {
                    // If we can't get the string, set the message equal to the key
                    sText = sStrTableKey;
                }

                // Finally add the column with the pay table text from the string table
                if ( !pDoc->AddChildNode( sParent, sColNode, sText ) )
                {
                    return false;
                }

            }
        }
    }

    return true;
}

//////////////////////////////////////////////////////////////////////////////
// AddLoginNumHands
//////////////////////////////////////////////////////////////////////////////
bool8 MHVPXMLTranslator::AddLoginNumHands( XMLDoc* pDoc,
									       uint8*  pBuff,
										   uint32& nOffset )
{

	if ( !pDoc->GetSingleNodeByte( TEXT("//num_hands"), m_nNumHands ) )
	{
		return false;
	}

	pBuff[nOffset++] = m_nNumHands;

	return true;
}


//****************************************************************************
// Incoming
//****************************************************************************

//////////////////////////////////////////////////////////////////////////////
// TranslateInLogin
//////////////////////////////////////////////////////////////////////////////
bool8 MHVPXMLTranslator::TranslateInLogin( XMLDoc* pDoc,
										   uint8* pGameInBuff,
										   uint32 nGameInBuffSize )
{
	////////////////////////////////////////////////////////////////
	// Login the user
	////////////////////////////////////////////////////////////////
    if ( !LoginUser( pDoc ) )
    {
        return false;
    }

    // Start by skipping the first byte of the message, that will be the
	//  length
	uint32 nOffset = 1;

	////////////////////////////////////////////////////////////////
	// Write Login for Multi-Hand Video Poker
	////////////////////////////////////////////////////////////////
	pGameInBuff[nOffset++] = 'M';
	pGameInBuff[nOffset++] = 'L';
	pGameInBuff[nOffset++] = 'V';
	pGameInBuff[nOffset++] = 'P';

	////////////////////////////////////////////////////////////////
	// Add Version Info
	////////////////////////////////////////////////////////////////
	if ( !AddLoginVersionInfo( nOffset, pDoc, pGameInBuff ) )
	{
		return false;
	}

	////////////////////////////////////////////////////////////////
	// Add the Machine ID
	////////////////////////////////////////////////////////////////
	if ( !AddLoginMachID( nOffset, pDoc, pGameInBuff ) )
	{
		return false;
	}

	////////////////////////////////////////////////////////////////
	// Add the Credit Value
	////////////////////////////////////////////////////////////////
	if ( !AddLoginCreditValue( nOffset, pDoc, pGameInBuff ) )
	{
		return false;
	}

	////////////////////////////////////////////////////////////////
	// Add the Number of Hands
	////////////////////////////////////////////////////////////////
	if ( !AddLoginNumHands( pDoc, pGameInBuff, nOffset ) )
	{
		return false;
	}

	////////////////////////////////////////////////////////////////
	// Add ID
	////////////////////////////////////////////////////////////////
	if ( !AddLoginID( nOffset, pDoc, pGameInBuff ) )
	{
		return false;
	}

	////////////////////////////////////////////////////////////////
	// play for money/play for fun
	////////////////////////////////////////////////////////////////
	if ( !AddLoginMoneyType( nOffset, pDoc, pGameInBuff ) )
	{
		return false;
	}

	////////////////////////////////////////////////////////////////
	// Cient type: CSN-7534
	////////////////////////////////////////////////////////////////
	if ( !AddClientID( nOffset, pDoc, pGameInBuff ) )
	{
		return false;
	}

	////////////////////////////////////////////////////////////////
	// Authentication token. CSN-7075: New Authentication
	////////////////////////////////////////////////////////////////
	if ( !AddAuthenticationToken( nOffset, pDoc, pGameInBuff ) )
	{
		return false;
	}

	////////////////////////////////////////////////////////////////
	// Finally, add message size
	////////////////////////////////////////////////////////////////
	pGameInBuff[0] = (uint8) nOffset;

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateInReload:  CSN-13423: reload with a new denomination
//////////////////////////////////////////////////////////////////////////////
bool8 MHVPXMLTranslator::TranslateInReload( XMLDoc* pDoc, uint8* pGameInBuff, uint32 nGameInBuffSize )
{
    // Start by skipping the first byte of the message, that will be the
	//  length
	uint32 nOffset = 1;

	////////////////////////////////////////////////////////////////
	// Write Reload for Video Poker
	////////////////////////////////////////////////////////////////
	pGameInBuff[nOffset++] = 'R';

	////////////////////////////////////////////////////////////////
	// Add the Credit Value
	////////////////////////////////////////////////////////////////
	if ( !AddLoginCreditValue( nOffset, pDoc, pGameInBuff ) )
	{
		return false;
	}

	////////////////////////////////////////////////////////////////
	// Add the Number of Hands
	////////////////////////////////////////////////////////////////
	if ( !AddLoginNumHands( pDoc, pGameInBuff, nOffset ) )
	{
		return false;
	}

	////////////////////////////////////////////////////////////////
	// Finally, add message size
	////////////////////////////////////////////////////////////////
	pGameInBuff[0] = (uint8) nOffset;

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateInDeal
//////////////////////////////////////////////////////////////////////////////
bool8 MHVPXMLTranslator::TranslateInDeal( XMLDoc* pDoc,
										  uint8* pGameInBuff,
										  uint32 nGameInBuffSize )
{
	// Message Size
	pGameInBuff[0] = 5;

	// Message ID
	pGameInBuff[1] = 'D';

	// Bet Amount
	uint32 nBetAmount;
	if ( !pDoc->GetSingleNodeLong( TEXT("//bet"), nBetAmount ) )
	{
		return false;
	}

	// NOTE: Copied this code from the VP client - I don't know why this is
	//  sent as a 24-bit number while pretty much all other games use
	//  32 bit numbers...
	pGameInBuff[2] = (uint8)(nBetAmount & 0xFF);
	pGameInBuff[3] = (uint8)((nBetAmount>>8) & 0xFF);
	pGameInBuff[4] = (uint8)((nBetAmount>>16) & 0xFF);

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateInHold
//////////////////////////////////////////////////////////////////////////////
bool8 MHVPXMLTranslator::TranslateInHold( XMLDoc* pDoc,
										  uint8* pGameInBuff,
										  uint32 nGameInBuffSize )
{
	// Message Size
	pGameInBuff[0] = 3;

	// Message ID
	pGameInBuff[1] = 'H';

	// Card Bits
	uint8 nCardBits = 0;

	// Set card bits
	for( uint8 i = 0; i < MAX_CARDS; i++ )
	{
		bool8 bFlag;

		String sNode;
		sNode.sprintf( TEXT("//card_%d"), i );

		if ( pDoc->GetSingleNodeBool( sNode, bFlag ) )
		{
            // If hold flag is set
            if ( bFlag )
            {
                // Turn on the bit
			    nCardBits |= (uint8)(1 << i);
            }
		}
	}

	// Set card bits into message
	pGameInBuff[2] = nCardBits;

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateInDoubleDeny
//////////////////////////////////////////////////////////////////////////////
bool8 MHVPXMLTranslator::TranslateInDoubleDeny( XMLDoc* pDoc,
												uint8* pGameInBuff,
												uint32 nGameInBuffSize )
{
	// Message Size
	pGameInBuff[0] = 2;

	// Message ID
	pGameInBuff[1] = 'X';

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateInDoubleDeal
//////////////////////////////////////////////////////////////////////////////
bool8 MHVPXMLTranslator::TranslateInDoubleDeal( XMLDoc* pDoc,
												uint8* pGameInBuff,
												uint32 nGameInBuffSize )
{
	// Message Size
	pGameInBuff[0] = 2;

	// Message ID
	pGameInBuff[1] = 'U';

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateInDoubleDraw
//////////////////////////////////////////////////////////////////////////////
bool8 MHVPXMLTranslator::TranslateInDoubleDraw( XMLDoc* pDoc,
												uint8* pGameInBuff,
												uint32 nGameInBuffSize )
{
	// Message Size
	pGameInBuff[0] = 3;

	// Message ID
	pGameInBuff[1] = 'u';

    // Card Index
	uint8 nByte;
	if ( !pDoc->GetSingleNodeByte( TEXT("//card_index"), nByte ) )
	{
		return false;
	}
    pGameInBuff[2] = nByte;

	return true;
}

//****************************************************************************
// Outgoing
//****************************************************************************

//////////////////////////////////////////////////////////////////////////////
// TranslateOutLogin
//////////////////////////////////////////////////////////////////////////////
bool8 MHVPXMLTranslator::TranslateOutLogin( uint8* pOutBuff,
										  	uint32 nOutBuffSize,
										  	uint8* pGameOutBuff,
										  	uint32 nGameOutBuffSize,
											bool isReload )
{
	XMLDoc doc;
    uint8 nPayTable = 0;

	if ( !doc.StartNew() )
	{
		return false;
	}

	if ( !doc.CreateRootNode( TEXT("game_msg") ) )
	{
		return false;
	}

	if (isReload)
	{
		if ( !doc.AddRootChildNode( TEXT("msg_id"), TEXT("Reload") ) )
		{
			return false;
		}
	}
	else
	{
		if ( !doc.AddRootChildNode( TEXT("msg_id"), TEXT("Login") ) )
		{
			return false;
		}
	}

	// Keep track of offset in buffer
	// MHVP uses 16 bit length
	uint32 nOffset = 3;

	// Balance
	int32 nValue;
	Endian::ReceiveInt4( &nValue, pGameOutBuff + nOffset );
	if ( !doc.AddRootChildNode( TEXT("balance"), (uint32) nValue ) )
	{
		return false;
	}
	nOffset += 4;

	// Maximum Bet
	uint8 nVal = pGameOutBuff[nOffset++];
	if ( !doc.AddRootChildNode( TEXT("max_bet"), (uint32) nVal ) )
	{
		return false;
	}

	// Pay Table
	nVal = pGameOutBuff[nOffset++];
	if ( !doc.AddRootChildNode( TEXT("pay_table"), (uint32) nVal ) )
	{
		return false;
	}
    nPayTable = nVal;

	// Denominations: CSN-13423: flash client (protocol version 1) needs credits as well
	doc.AddRootChildNode( TEXT("Denominations") );
	Endian::ReceiveInt4( &nValue, pGameOutBuff + nOffset );;	// Number of denominations	
	doc.AddChildNode( TEXT("//Denominations"), TEXT("num_denominations"), (uint32) nValue );
	nOffset += 4;
    for(int32 nI=0;nI<5;++nI)
    {	    
		String strTagName;
        strTagName.sprintf("denom_value_%d",nI);
	    Endian::ReceiveInt4( &nValue, pGameOutBuff + nOffset );
	    if ( !doc.AddChildNode( TEXT("//Denominations"), strTagName, (uint32) nValue ) )
	    {
		    return false;
	    }
	    nOffset += 4;
    }

	// Saved Game?
	bool8 bSaved = pGameOutBuff[nOffset++];
	if ( !doc.AddRootChildNode( TEXT("saved"), bSaved ) )
	{
		return false;
	}

	// Create saved game data if we have it
	if ( bSaved )
	{
		// Add saved game node
		if ( !doc.AddRootChildNode( TEXT("saved_game") ) )
		{
			return false;
		}

		// Restore Type
		uint8 nRestoreType = pGameOutBuff[nOffset++];
		if ( !doc.AddChildNode( TEXT("//saved_game"), TEXT("restore_type"), (uint32) nRestoreType ) )
		{
			return false;
		}

		// Paytable (exists in all 3 restore types)
		nVal = pGameOutBuff[nOffset++];
		if ( !doc.AddChildNode( TEXT("//saved_game"), TEXT("pay_table"), (uint32) nVal ) )
		{
			return false;
		}
        nPayTable = nVal;

		// Bet (exists in all 3 restore types)
		Endian::ReceiveInt4( &nValue, pGameOutBuff + nOffset );
		if ( !doc.AddChildNode( TEXT("//saved_game"), TEXT("bet"), (uint32) nValue ) )
		{
			return false;
		}
		nOffset += 4;

		switch ( nRestoreType )
		{
		case 0:
       		// Win type
       		nVal = pGameOutBuff[nOffset++];
       		if ( !doc.AddChildNode( TEXT("//saved_game"), TEXT("win_type"), (uint32) nVal ) )
       		{
       			return false;
       		}

			// Add cards
			if ( !AddCards( &doc, pGameOutBuff, nOffset, TEXT("//saved_game") ) )
			{
				return false;
			}
			break;

		case 1:
		    // Get the bits from the buffer
		    // bwardlow 4-Oct-2006 #15651 this value is not a bitfield, just a number
			{
			    uint8 nHeld = pGameOutBuff[ nOffset++ ];
				// bwardlow 26-Oct-2006 #16253 actually, sometimes this is a bitfield (for the first
				// double up, where multiple cards may have been held in the winning hand) and sometimes
				// it is a number indicating which individual card was held (for subsequent double ups)
				
				// note no increment in the following statement -- we want to leave the value in the buffer
				// also note that we have to account for double_up_payout here, even though we didn't for 
				// the single-hand case, because the buffers are constructed slightly differently
				uint8 nDblWinType = pGameOutBuff[ nOffset + sizeof( nValue ) ];

			    for ( uint8 i = 0; i < MAX_CARDS; i++ )
			    {
				    // Build XML node string
				    String sNode;
				    sNode.sprintf( TEXT("held_%d"), i );
				    
					// one of these days, we should re-engineer this to be even more like the single-hand
					// video poker server so we would have an enum like this available
					//enum VPDoubleUpResults
					//{
					//	VPEVT_DBLUP_LOSER   = 0xfc,
					//	VPEVT_DBLUP_WON     = 0xfe,
					//	VPEVT_DBLUP_TIE     = 0xfd
					//};
					// instead of having to copy this from vpgame.cpp
					#define VPEVT_DBLUP_WON			0xfe
					#define VPEVT_DBLUP_LOSER		0xfc
					#define VPEVT_DBLUP_TIE			0xfd

					// Write XML
					if ( nDblWinType == VPEVT_DBLUP_WON || nDblWinType == VPEVT_DBLUP_TIE )
					{
						// indicates the existence of a previous result, therefore this is not
						// the first game and only one card was held in the last hand
						if ( !doc.AddChildNode( TEXT("//saved_game"), sNode, (bool8) ( nHeld == i ) ) )
						{
							return false;
						}
					}
					else
					{
						// no previous result indicates that this is the first double up hand and multiple
						// cards may have been held in the last hand
						if ( !doc.AddChildNode( TEXT("//saved_game"), sNode, (bool8) ( nHeld & ( 1 << i ) ) ) )
						{
							return false;
						}
					}
				}
			}

	        // Double Up Payout
	        Endian::ReceiveInt4( &nValue, pGameOutBuff + nOffset );
    	    if ( !doc.AddChildNode( TEXT("//saved_game"), TEXT("double_payout"), (uint32) nValue ) )
        	{
	        	return false;
	        }
	        nOffset += 4;

	        // Double Up Win Type
	        nVal = pGameOutBuff[nOffset++];
    	    if ( !doc.AddChildNode( TEXT("//saved_game"), TEXT("double_win_type"), (uint32) nVal ) )
	        {
		        return false;
    	    }

	        // Double Up Won
	        nVal = pGameOutBuff[nOffset++];
    	    if ( !doc.AddChildNode( TEXT("//saved_game"), TEXT("double_won"), (bool8) nVal ) )
        	{
	        	return false;
	        }

			// Add multiple hands
			if ( !AddMultiHands( &doc, pGameOutBuff, nOffset, TEXT("//saved_game"), m_nNumHands ) )
			{
				return false;
			}
			break;

		case 2:
	        // Double Up Win Type
	        nVal = pGameOutBuff[nOffset++];
    	    if ( !doc.AddChildNode( TEXT("//saved_game"), TEXT("double_win_type"), (uint32) nVal ) )
	        {
		        return false;
    	    }

       		// Win type
       		nVal = pGameOutBuff[nOffset++];
       		if ( !doc.AddChildNode( TEXT("//saved_game"), TEXT("win_type"), (uint32) nVal ) )
       		{
       			return false;
       		}

	        // Double Up Won
    	    nVal = pGameOutBuff[nOffset++];
        	if ( !doc.AddChildNode( TEXT("//saved_game"), TEXT("double_won"), (bool8) nVal ) )
	        {
		        return false;
    	    }

        	// Double Up Payout
	        Endian::ReceiveInt4( &nValue, pGameOutBuff + nOffset );
	        if ( !doc.AddChildNode( TEXT("//saved_game"), TEXT("double_payout"), (uint32) nValue ) )
    	    {
	    	    return false;
	        }
	        nOffset += 4;

			// Double up face card
			Card c;
			nOffset += c.deSerialize( pGameOutBuff + nOffset );
			if ( !doc.AddChildNode( TEXT("//saved_game"), TEXT("double_face_card"), c ) )
			{
				return false;
			}

			// Add multiple hands
			if ( !AddMultiHands( &doc, pGameOutBuff, nOffset, TEXT("//saved_game"), m_nNumHands - 1 ) )
			{
				return false;
			}
			break;
		}
	}

    // Now add the pay table info
    if ( !AddPayTableInfo( &doc, nPayTable ) )
    {
        return false;
    }

	if ( !AddCurrencyOutput(&doc) )	// CSN-11204: return currency
	{
		return false;
	}

	// Done - send the actual XML
	if ( !doc.SendXML( pOutBuff, nOutBuffSize ) )
	{
		return false;
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateOutDeal
//////////////////////////////////////////////////////////////////////////////
bool8 MHVPXMLTranslator::TranslateOutDeal( uint8* pOutBuff,
										   uint32 nOutBuffSize,
										   uint8* pGameOutBuff,
										   uint32 nGameOutBuffSize )
{
	XMLDoc doc;

	if ( !doc.StartNew() )
	{
		return false;
	}

	if ( !doc.CreateRootNode( TEXT("game_msg") ) )
	{
		return false;
	}

	if ( !doc.AddRootChildNode( TEXT("msg_id"), TEXT("Deal") ) )
	{
		return false;
	}

	// Keep track of offset in buffer
	// MHVP uses 16 bit length
	uint32 nOffset = 3;

	// Add the cards
	if ( !AddCards( &doc, pGameOutBuff, nOffset, TEXT("") ) )
	{
		return false;
	}

	// for some reason the win_type and balance are reversed from VP
	// Balance
	int32 nValue;
	Endian::ReceiveInt4( &nValue, pGameOutBuff + nOffset );
	if ( !doc.AddRootChildNode( TEXT("balance"), (uint32) nValue ) )
	{
		return false;
	}
	nOffset += 4;

	// Win Type
	uint8 nVal = pGameOutBuff[nOffset++];
	if ( !doc.AddRootChildNode( TEXT("win_type"), (uint32) nVal ) )
	{
		return false;
	}

	// Done - send the actual XML
	if ( !doc.SendXML( pOutBuff, nOutBuffSize ) )
	{
		return false;
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateOutSubGame
//////////////////////////////////////////////////////////////////////////////
bool8 MHVPXMLTranslator::TranslateOutSubGame( uint8* pOutBuff,
									          uint32 nOutBuffSize,
										      uint8* pGameOutBuff,
										      uint32 nGameOutBuffSize )
{
	XMLDoc doc;

	if ( !doc.StartNew() )
	{
		return false;
	}

	if ( !doc.CreateRootNode( TEXT("game_msg") ) )
	{
		return false;
	}

	if ( !doc.AddRootChildNode( TEXT("msg_id"), TEXT("SubGame") ) )
	{
		return false;
	}

	// Data 0
	uint8 nVal = pGameOutBuff[2];
	if ( !doc.AddRootChildNode( TEXT("data_0"), (uint32) nVal ) )
	{
		return false;
	}

	// Data 1
	nVal = pGameOutBuff[3];
	if ( !doc.AddRootChildNode( TEXT("data_1"), (uint32) nVal ) )
	{
		return false;
	}

	// Done - send the actual XML
	if ( !doc.SendXML( pOutBuff, nOutBuffSize ) )
	{
		return false;
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateOutHold
//////////////////////////////////////////////////////////////////////////////
bool8 MHVPXMLTranslator::TranslateOutHold( uint8* pOutBuff,
									       uint32 nOutBuffSize,
							   	           uint8* pGameOutBuff,
										   uint32 nGameOutBuffSize )
{
	XMLDoc doc;

	if ( !doc.StartNew() )
	{
		return false;
	}

	if ( !doc.CreateRootNode( TEXT("game_msg") ) )
	{
		return false;
	}

	if ( !doc.AddRootChildNode( TEXT("msg_id"), TEXT("Hold") ) )
	{
		return false;
	}

	// Keep track of offset in buffer
	// MHVP uses 16 bit length
	uint32 nOffset = 3;

	// Add multiple hands
	if ( !AddMultiHands( &doc, pGameOutBuff, nOffset, TEXT(""), m_nNumHands ) )
	{
		return false;
	}

	// Payout
	int32 nValue;
	Endian::ReceiveInt4( &nValue, pGameOutBuff + nOffset );
	if ( !doc.AddRootChildNode( TEXT("payout"), (uint32) nValue ) )
	{
		return false;
	}
	nOffset += 4;

	// Double Up Status
	uint8 nVal = pGameOutBuff[nOffset++];
	if ( !doc.AddRootChildNode( TEXT("double_status"), (bool8) nVal ) )
	{
		return false;
	}

	// Double Up Win Type
	nVal = pGameOutBuff[nOffset++];
	if ( !doc.AddRootChildNode( TEXT("double_win_type"), (uint32) nVal ) )
	{
		return false;
	}

	// Double Up Won
	nVal = pGameOutBuff[nOffset++];
	if ( !doc.AddRootChildNode( TEXT("double_won"), (bool8) nVal ) )
	{
		return false;
	}

	// Double Up Payout
	Endian::ReceiveInt4( &nValue, pGameOutBuff + nOffset );
	if ( !doc.AddRootChildNode( TEXT("double_payout"), (uint32) nValue ) )
	{
		return false;
	}
	nOffset += 4;

	// Balance
	Endian::ReceiveInt4( &nValue, pGameOutBuff + nOffset );
	if ( !doc.AddRootChildNode( TEXT("balance"), (uint32) nValue ) )
	{
		return false;
	}
	nOffset += 4;

	// Done - send the actual XML
	if ( !doc.SendXML( pOutBuff, nOutBuffSize ) )
	{
		return false;
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateOutOptions
//////////////////////////////////////////////////////////////////////////////
bool8 MHVPXMLTranslator::TranslateOutOptions( uint8* pOutBuff,
									          uint32 nOutBuffSize,
							   	              uint8* pGameOutBuff,
										      uint32 nGameOutBuffSize )
{
	XMLDoc doc;

	if ( !doc.StartNew() )
	{
		return false;
	}

	if ( !doc.CreateRootNode( TEXT("game_msg") ) )
	{
		return false;
	}

	if ( !doc.AddRootChildNode( TEXT("msg_id"), TEXT("Options") ) )
	{
		return false;
	}

	// Keep track of offset in buffer
	// MHVP uses 16 bit length
	uint32 nOffset = 3;

	// Pay Table Changed
	uint8 nVal = pGameOutBuff[nOffset++];
	if ( !doc.AddRootChildNode( TEXT("pay_table_changed"), (bool8) nVal ) )
	{
		return false;
	}

	// New Pay Table
	nVal = pGameOutBuff[nOffset++];
	if ( !doc.AddRootChildNode( TEXT("pay_table"), (uint32) nVal ) )
	{
		return false;
	}

	// Double Up State Changed
	nVal = pGameOutBuff[nOffset++];
	if ( !doc.AddRootChildNode( TEXT("double_state_changed"), (bool8) nVal ) )
	{
		return false;
	}

	// New Double Up State
	nVal = pGameOutBuff[nOffset++];
	if ( !doc.AddRootChildNode( TEXT("double_state"), (uint32) nVal ) )
	{
		return false;
	}

	// Done - send the actual XML
	if ( !doc.SendXML( pOutBuff, nOutBuffSize ) )
	{
		return false;
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateOutDoubleDeny
//////////////////////////////////////////////////////////////////////////////
bool8 MHVPXMLTranslator::TranslateOutDoubleDeny( uint8* pOutBuff,
									             uint32 nOutBuffSize,
							   	                 uint8* pGameOutBuff,
										         uint32 nGameOutBuffSize )
{
	XMLDoc doc;

	if ( !doc.StartNew() )
	{
		return false;
	}

	if ( !doc.CreateRootNode( TEXT("game_msg") ) )
	{
		return false;
	}

	if ( !doc.AddRootChildNode( TEXT("msg_id"), TEXT("Double") ) )
	{
		return false;
	}

    if ( !doc.AddRootChildNode( TEXT("action"), TEXT("Deny") ) )
	{
		return false;
	}

	// Keep track of offset in buffer
	// MHVP uses 16 bit length
	uint32 nOffset = 3;

	// Double up win type
	uint8 nVal = pGameOutBuff[nOffset++];
	if ( !doc.AddRootChildNode( TEXT("double_win_type"), (uint32) nVal ) )
	{
		return false;
	}

    // Double Up Payout
    int32 nValue;
	Endian::ReceiveInt4( &nValue, pGameOutBuff + nOffset );
	if ( !doc.AddRootChildNode( TEXT("double_payout"), (uint32) nValue ) )
	{
		return false;
	}
	nOffset += 4;

	// Balance
	Endian::ReceiveInt4( &nValue, pGameOutBuff + nOffset );
	if ( !doc.AddRootChildNode( TEXT("balance"), (uint32) nValue ) )
	{
		return false;
	}
	nOffset += 4;

	// Done - send the actual XML
	if ( !doc.SendXML( pOutBuff, nOutBuffSize ) )
	{
		return false;
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateOutDoubleDeal
//////////////////////////////////////////////////////////////////////////////
bool8 MHVPXMLTranslator::TranslateOutDoubleDeal( uint8* pOutBuff,
									             uint32 nOutBuffSize,
							   	                 uint8* pGameOutBuff,
										         uint32 nGameOutBuffSize )
{
	XMLDoc doc;

	if ( !doc.StartNew() )
	{
		return false;
	}

	if ( !doc.CreateRootNode( TEXT("game_msg") ) )
	{
		return false;
	}

	if ( !doc.AddRootChildNode( TEXT("msg_id"), TEXT("Double") ) )
	{
		return false;
	}

    if ( !doc.AddRootChildNode( TEXT("action"), TEXT("Deal") ) )
	{
		return false;
	}

	// Keep track of offset in buffer
	// MHVP uses 16 bit length
	uint32 nOffset = 3;
	
    // Double up face card
    Card c;
    nOffset += c.deSerialize( pGameOutBuff + nOffset );
	if ( !doc.AddRootChildNode( TEXT("double_card"), c ) )
	{
	    return false;
	}

	// Balance
    int32 nValue;
	Endian::ReceiveInt4( &nValue, pGameOutBuff + nOffset );
	if ( !doc.AddRootChildNode( TEXT("balance"), (uint32) nValue ) )
	{
		return false;
	}
	nOffset += 4;

	// Done - send the actual XML
	if ( !doc.SendXML( pOutBuff, nOutBuffSize ) )
	{
		return false;
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateOutDoubleDraw
//////////////////////////////////////////////////////////////////////////////
bool8 MHVPXMLTranslator::TranslateOutDoubleDraw( uint8* pOutBuff,
									             uint32 nOutBuffSize,
							   	                 uint8* pGameOutBuff,
										         uint32 nGameOutBuffSize )
{
	XMLDoc doc;

	if ( !doc.StartNew() )
	{
		return false;
	}

	if ( !doc.CreateRootNode( TEXT("game_msg") ) )
	{
		return false;
	}

	if ( !doc.AddRootChildNode( TEXT("msg_id"), TEXT("Double") ) )
	{
		return false;
	}

    if ( !doc.AddRootChildNode( TEXT("action"), TEXT("Draw") ) )
	{
		return false;
	}

	// Keep track of offset in buffer
	// MHVP uses 16 bit length
	uint32 nOffset = 3;

	// Add the cards
	if ( !AddCards( &doc, pGameOutBuff, nOffset, TEXT("") ) )
	{
		return false;
	}

	// Double Up Status
	uint8 nVal = pGameOutBuff[nOffset++];
	if ( !doc.AddRootChildNode( TEXT("double_status"), (bool8) nVal ) )
	{
		return false;
	}

	// Double Up Win Type
	nVal = pGameOutBuff[nOffset++];
	if ( !doc.AddRootChildNode( TEXT("double_win_type"), (uint32) nVal ) )
	{
		return false;
	}

	// Double Up Won
	nVal = pGameOutBuff[nOffset++];
	// bwardlow 21-Sep-2006 #15444 double_won is not a boolean
	if ( !doc.AddRootChildNode( TEXT("double_won"), (uint32) nVal ) )
	{
		return false;
	}

	// Double Up Payout
    int32 nValue;
	Endian::ReceiveInt4( &nValue, pGameOutBuff + nOffset );
	if ( !doc.AddRootChildNode( TEXT("double_payout"), (uint32) nValue ) )
	{
		return false;
	}
	nOffset += 4;

	// Balance
	Endian::ReceiveInt4( &nValue, pGameOutBuff + nOffset );
	if ( !doc.AddRootChildNode( TEXT("balance"), (uint32) nValue ) )
	{
		return false;
	}
	nOffset += 4;

	// Done - send the actual XML
	if ( !doc.SendXML( pOutBuff, nOutBuffSize ) )
	{
		return false;
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateOutError
//////////////////////////////////////////////////////////////////////////////
bool8 MHVPXMLTranslator::TranslateOutError( uint8* pOutBuff,
										    uint32 nOutBuffSize,
										    uint8* pGameOutBuff,
										    uint32 nGameOutBuffSize )
{
	bool8 bRetVal = false;

	// MHVP uses 16 bit length
    if ( pGameOutBuff[2] == '$' )
	{
		bRetVal = ReturnErrorMessage( TEXT("InsufficientFunds"), pOutBuff, nOutBuffSize );
	}
	else if ( pGameOutBuff[2] == '!' )
	{
		bRetVal = ReturnErrorMessage( TEXT("Shutdown"), pOutBuff, nOutBuffSize );
	}
	else if ( pGameOutBuff[2] == 'B' )
	{
		bRetVal = ReturnErrorMessage( TEXT("InvalidBet"), pOutBuff, nOutBuffSize );
	}
	else if ( pGameOutBuff[2] == 'N' )
	{
        // kharmon.  02-21-07.  MCA #29819.  Need to send back different messages for auto and admin logoffs.
        String strErrorName = "InvalidAction";
        int16 nLen = 0;
        Endian::ReceiveInt2( &nLen, pGameOutBuff );

        if(nLen==7)  // The extra 4 bytes have the error code.
        {
            int32 nErrorCode=0;
            Endian::ReceiveInt4( &nErrorCode, &pGameOutBuff[3] );

            switch(nErrorCode)
            {
                case TrackSQL::AdminLogout:
                {
                    strErrorName = "AdminLogout";
                    break;
                }
                case TrackSQL::AutoLogout:
                {
                    strErrorName = "TimedOut";
                    break;
                }
            }            
        }

        bRetVal = ReturnErrorMessage( strErrorName, pOutBuff, nOutBuffSize );
	}
	else if ( pGameOutBuff[2] == 'G' )
	{
		bRetVal = ReturnErrorMessage( TEXT("InProgress"), pOutBuff, nOutBuffSize );
	}

	return bRetVal;
}
